﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ComputerArchitecture.Core;
using ComputerArchitecture.Core.Logging;
using ComputerArchitecture.Numbers;
using ComputerArchitecture.Units.Integers.Signed.Interfaces;

namespace ComputerArchitecture.Units.Integers.Signed.Implementations
{
    public class SignedDivideUnit : LoggingObject, ISignedDivideUnit
    {
        #region Fields

        private readonly ISignedAdderUnit _adderUnit;
        private readonly ISignedNegateUnit _negateUnit;
        private readonly ISignedSubstractUnit _substractUnit;

        #endregion

        #region Constructors

        public SignedDivideUnit(ILoggingService loggingService, ISignedAdderUnit adderUnit, ISignedNegateUnit negateUnit, ISignedSubstractUnit substractUnit) 
            : base(loggingService)
        {
            ArgumentValidator.NullValidate(adderUnit, "adderUnit");
            _adderUnit = adderUnit;
            ArgumentValidator.NullValidate(negateUnit, "negateUnit");
            _negateUnit = negateUnit;
            ArgumentValidator.NullValidate(substractUnit, "substractUnit");
            _substractUnit = substractUnit;
        }

        public SignedDivideUnit(ILoggingService loggingService)
            : this(loggingService, new SignedAdderUnit(loggingService), new SignedNegateUnit(loggingService), new SignedSubstractUnit(loggingService))
        {
        }

        #endregion

        IBitNumber IDivideUnit.Divide(IBitNumber dividend, IBitNumber divisor, out IBitNumber remainder)
        {
            ISignedBitNumber signedRemainder;
            var result = this.Divide((ISignedBitNumber)dividend, (ISignedBitNumber)divisor, out signedRemainder);
            remainder = signedRemainder;
            return result;
        }

        public IBitNumber Divide(IBitNumber dividendMoreSignificant, IBitNumber dividendLessSignificant, IBitNumber divisor, out IBitNumber remainder)
        {
            ISignedBitNumber signedRemainder;
            var result = this.Divide((ISignedBitNumber)dividendMoreSignificant, (ISignedBitNumber)dividendLessSignificant, (ISignedBitNumber)divisor, out signedRemainder);
            remainder = signedRemainder;
            return result;
        }

        public ISignedBitNumber Divide(ISignedBitNumber dividendMoreSignificant, ISignedBitNumber dividendLessSignificant, ISignedBitNumber divisor, out ISignedBitNumber remainder)
        {
            ArgumentValidator.NullValidate(dividendMoreSignificant);
            ArgumentValidator.NullValidate(dividendLessSignificant);
            ArgumentValidator.NullValidate(divisor);
            // check if divisor equals null
            ISignedBitNumber zero = SignedBitNumber.GetZero(divisor.Size);
            if (divisor.Equals(zero))
            {
                throw new DivideByZeroException();
            }
            // todo: size checking;
            // make local copy of dividend's registers
            ISignedBitNumber dividendMoreSignificantLocal = (ISignedBitNumber)dividendMoreSignificant.Clone(),
                       dividendLessSignificantLocal = (ISignedBitNumber)dividendLessSignificant.Clone();

            for (int i = 0; i < divisor.Size; ++i)
            {
                this.LoggingService.LogInfo("Iteration {0}", i + 1);
                // shift left a and q
                {
                    Bit lessSignificantCarryBit, moreSignificantCarryBit;
                    this.LoggingService.LogInfo("Shift left dividend less significant bits");
                    dividendLessSignificantLocal.LogicShiftLeft(1, out lessSignificantCarryBit);
                    this.LoggingService.LogInfo("result: {0:d4}", dividendLessSignificantLocal);
                    // todo: check if we need to assign the least significant bit as lessSignificantCarryBit
                    this.LoggingService.LogInfo("Shift left dividend more significant bits and set the least significant bit as carry bit from less significant bits shift");
                    dividendMoreSignificantLocal.LogicShiftLeft(1, out moreSignificantCarryBit);
                    dividendMoreSignificantLocal[dividendMoreSignificantLocal.Size - 1].SetAs(lessSignificantCarryBit);
                    this.LoggingService.LogInfo("result: {0:d4}", dividendMoreSignificantLocal);
                }
                // if sign of more significant bits of dividend is equals divisor sign
                //     a = a - m
                // else
                //     a = a + m
                bool overflow = false;
                int beforeOperationMoreSignificantBitsSign = dividendMoreSignificantLocal.Sign;
                ISignedBitNumber temp = (ISignedBitNumber)dividendMoreSignificantLocal.Clone();
                if (dividendMoreSignificantLocal.Sign == divisor.Sign)
                {
                    this.LoggingService.LogInfo("More significant's sign equals divisor's sign - substract");
                    dividendMoreSignificantLocal = _substractUnit.Substract(dividendMoreSignificantLocal, divisor,
                                                                            out overflow);
                }
                else
                {
                    this.LoggingService.LogInfo("More significant's sign does't equal divisor's sign - add");
                    dividendMoreSignificantLocal = _adderUnit.Add(dividendMoreSignificantLocal, divisor, out overflow);
                }
                this.LoggingService.LogInfo("dividend more significant bits: {0:d4}", dividendMoreSignificantLocal);
                // check successful or unsuccessful
                // successful = a-sign is not changed
                if (dividendMoreSignificantLocal.Sign == beforeOperationMoreSignificantBitsSign | (dividendMoreSignificantLocal.Equals(zero) & dividendLessSignificantLocal.Equals(zero)))
                {
                    // success
                    // set Q_0
                    this.LoggingService.LogInfo("Dividend more significant bits' sign hasn't changed: success, set bit in less significant");
                    dividendLessSignificantLocal[dividendLessSignificantLocal.Size - 1].Set();
                }
                else
                {
                    this.LoggingService.LogInfo("Dividend more significant bits' sign changed: unsuccessful, restore dividend more significant and reset bit in less significant bits");
                    // reset q_0
                    dividendLessSignificantLocal[dividendLessSignificantLocal.Size - 1].Reset();
                    // restore a
                    dividendMoreSignificantLocal = temp;
                }
                this.LoggingService.LogInfo("Dividend less significant bits: {0:d4}\n", dividendLessSignificantLocal);
            }

            this.LoggingService.LogInfo("Remainder in more significant dividend's bits");
            remainder = dividendMoreSignificantLocal;
            this.LoggingService.LogInfo("Remainder: {0:d4}", remainder);
            this.LoggingService.LogInfo("Result in less significant dividend's bits");
            if (dividendMoreSignificant.Sign == divisor.Sign)
            {
                this.LoggingService.LogInfo("divident more significant sign equals divisor's sign: return");
                return dividendLessSignificantLocal;
            }
            this.LoggingService.LogInfo("divident more significant sign doesn't equal divisor's: negate result ");
            bool hasError = false;
            NegateSpecialCase specialCase = NegateSpecialCase.None;
            ISignedBitNumber result = _negateUnit.Negate(dividendLessSignificantLocal, out specialCase, out hasError);
            return specialCase == NegateSpecialCase.MaxNegativeValue ? dividendLessSignificantLocal : result;
        }

        public ISignedBitNumber Divide(ISignedBitNumber dividend, ISignedBitNumber divisor, out ISignedBitNumber remainder)
        {
            var localDividend = (ISignedBitNumber) dividend.Clone();
            if (dividend.Size == divisor.Size)
            {
                localDividend.Expand(dividend.Size*2);
            }

            ISignedBitNumber dividendMoreSignificant = new SignedBitNumber(divisor.Size), dividendLessSignificant = new SignedBitNumber(divisor.Size);
            localDividend.CopyTo(dividendMoreSignificant, 0, 0, dividendMoreSignificant.Size);
            localDividend.CopyTo(dividendLessSignificant, 0, dividendMoreSignificant.Size, dividendLessSignificant.Size);
            return this.Divide(dividendMoreSignificant, dividendLessSignificant, divisor, out remainder);
            
        }
    }
}
